# -*- coding: utf-8 -*-
'''
@author : 景色
@csdn : https://blog.csdn.net/pjjing
@QQ群 : 767101469
@公众号 : QuantRoad2019

穿透式版本认证及报单demo，用于6.3.13及以上版本API
'''
import thosttraderapi as api
from winsound import Beep
from threading import Thread
from time import sleep,ctime
import sys,csv
from utils import getTradeDate
from PyQt5.QtCore import pyqtSignal,QObject
# from user_info import ctp_ZG_config as config
from user_info import ctp_BC_config as config
# from user_info import ctp_DC_config as config
# from user_info import ctp_simnow_config as config
from PyQt5.QtWidgets import QMainWindow, QApplication
from user_info import user_info as info 
from user_info import orderStatusDict,OffsetFlagDict,OpenCloseFlagDict,OrderSubmitStatusDict
import outBoll
#交易日期，形如20230519
TradeDateStr=getTradeDate()
#Addr
FrontAddr=config['trader_server']
#LoginInfo
BROKERID=config['broker_id']
USERID=config['investor_id']
PASSWORD=config['password']
APPID=config['app_id']
AUTHCODE=config['auth_code']
print(BROKERID,USERID,APPID,AUTHCODE)
orderfield=api.CThostFtdcInputOrderField()
orderfield.BrokerID=BROKERID
orderfield.ExchangeID='CZCE'
orderfield.InstrumentID='MA309'
orderfield.UserID=USERID
orderfield.InvestorID=USERID
orderfield.Direction='0'
orderfield.LimitPrice=0
orderfield.VolumeTotalOriginal=1
orderfield.OrderPriceType=api.THOST_FTDC_OPT_LimitPrice
orderfield.ContingentCondition = api.THOST_FTDC_CC_Immediately
orderfield.TimeCondition = api.THOST_FTDC_TC_GFD
orderfield.VolumeCondition = api.THOST_FTDC_VC_AV
orderfield.CombHedgeFlag=api.THOST_FTDC_HF_Speculation#投机，不是套保
        #TFtdcOffsetFlagEnType是一个开平标志类型
        #TFtdcOffsetFlagType是一个开平标志类型
orderfield.CombOffsetFlag=api.THOST_FTDC_OF_Open
orderfield.GTDDate=""
orderfield.orderfieldRef="1"
orderfield.MinVolume = 0
orderfield.ForceCloseReason = api.THOST_FTDC_FCC_NotForceClose
orderfield.IsAutoSuspend = 0
	
#撤单
DeleteOrderField=api.CThostFtdcInputOrderActionField()
DeleteOrderField.BrokerID=BROKERID
DeleteOrderField.ExchangeID='DCE'
DeleteOrderField.UserID=USERID
DeleteOrderField.OrderSysID='0000000000'
DeleteOrderField.InstrumentID='m2309'
DeleteOrderField.ActionFlag = api.THOST_FTDC_AF_Delete
class CTradeSpi(api.CThostFtdcTraderSpi):
    tapi=''
    def __init__(self,tapi,info,signalHandle,orderfield):
        api.CThostFtdcTraderSpi.__init__(self)
        self.tapi=tapi
        self.info=info
        self.orderDict={}
        for k in self.info['instrumenID_dc']:
            self.orderDict[k]={
                #开仓挂单
                'orderInQueue':{'direction':0,'volume':0,'OrderSysID':0},#挂单，方向，数量
                'positionLong':{'direction':0,'volume':0},
                'profitCoin':3,
                "tradedCouple":{'openPrice':0,
                                'openTime':'',
                                'closePrice':0,
                                'closeTime':'',
                                'tragyName':'手动进仓',
                                'tragyArg':'',
                                "instrumentID":k
                                }
                }
        self.signalHandle=signalHandle
        self.signalHandle.OrderSignal.connect(self.orderSignalHandle)
        self.orderfield=orderfield
    def OnRtnOrder(self, pOrder: 'CThostFtdcOrderField') -> "void":
        '''报单响应'''
        #开仓成交 持仓增加

        #平仓成交 持仓减少
        if pOrder.OrderStatus=='a':return
        #未成交还在队列中
        if pOrder.InstrumentID not in self.orderDict:return
        if pOrder.OrderSysID == self.orderDict[pOrder.InstrumentID]['orderInQueue']['OrderSysID']:
            if pOrder.OrderStatus==api.THOST_FTDC_OST_Canceled:
                #撤单 则挂单为0  有问题，有可能撤的是平仓单
                self.orderDict[pOrder.InstrumentID]['orderInQueue']['volume']=0
                print('撤单',pOrder.InstrumentID)
        else:
            # print(pOrder.UpdateTime,OpenCloseFlagDict[pOrder.CombOffsetFlag],OffsetFlagDict[pOrder.Direction],orderStatusDict[pOrder.OrderStatus],OrderSubmitStatusDict[pOrder.OrderSubmitStatus],pOrder.OrderSysID)
            if pOrder.OrderStatus==api.THOST_FTDC_OST_NoTradeQueueing:
                # 开仓报单
                if pOrder.CombOffsetFlag==api.THOST_FTDC_OF_Open:
                    self.orderDict[pOrder.InstrumentID]['orderInQueue']['volume']+=pOrder.VolumeTotalOriginal
                    self.orderDict[pOrder.InstrumentID]['orderInQueue']['OrderSysID']=pOrder.OrderSysID
                else:#平仓报单
                    pass
            print(ctime().split(' ')[3],pOrder.InstrumentID,OffsetFlagDict[pOrder.Direction],OpenCloseFlagDict[pOrder.CombOffsetFlag],pOrder.VolumeTotalOriginal,'手','挂单合计：',self.orderDict[pOrder.InstrumentID]['orderInQueue']['volume'],pOrder.OrderSysID)

            # print(pOrder.InstrumentID,OffsetFlagDict[pOrder.Direction],OpenCloseFlagDict[pOrder.CombOffsetFlag],'挂单',self.orderDict[pOrder.InstrumentID]['orderInQueue']['volume'],pOrder.OrderSysID)
            
    def OnRtnTrade(self,pTrade:'CThostFtdcTradeField' )-> "void":
        '''成交响应'''
        Beep(200,200)
        #开仓
        #挂单未成交 挂单增加
        #挂单成交，挂单减少，持仓增加   
        #  自动挂平仓 self.orderDict[InstrumentID]['profitCoin']
        # print('开平标志：',pTrade.OffsetFlag,'   成交数量',pTrade.Volume)
        #如果预期挂单点数为0 ，则为手动平仓单，不处理
        
        if pTrade.InstrumentID not in self.orderDict:return#有时候，手动进仓，品种不在列表中，引起错误
        msg=pTrade.TradeTime+' '+pTrade.InstrumentID+' '
        #开仓成交 挂单减少，持仓增加
        if pTrade.OffsetFlag==api.THOST_FTDC_OF_Open:
            msg+='开仓成交 '
            # print('买开 %s %d手'%(pTrade.InstrumentID,pTrade.Volume))
            self.orderDict[pTrade.InstrumentID]['orderInQueue']['volume']-=pTrade.Volume
            self.orderDict[pTrade.InstrumentID]['positionLong']['volume']+=pTrade.Volume
            self.orderDict[pTrade.InstrumentID]['tradedCouple']['openPrice']=pTrade.Price
            self.orderDict[pTrade.InstrumentID]['tradedCouple']['openTime']=pTrade.TradeTime
            if pTrade.Direction==api.THOST_FTDC_D_Buy:#买开
                msg+='买入 '
                Direction=api.THOST_FTDC_D_Sell
                LimitPrice=pTrade.Price + self.orderDict[pTrade.InstrumentID]['profitCoin']*self.info['instrumenID_dc'][pTrade.InstrumentID]['miniPrice']
                #不管开仓平仓，买入是成本，属于减数
                self.orderDict[pTrade.InstrumentID]['tradedCouple']['openPrice']=-pTrade.Price
            else:#卖开
                msg+='卖出 '
                Direction=api.THOST_FTDC_D_Buy
                LimitPrice=pTrade.Price - self.orderDict[pTrade.InstrumentID]['profitCoin']*self.info['instrumenID_dc'][pTrade.InstrumentID]['miniPrice']
            # msg+=str(round(pTrade.Price,2))
            volume=pTrade.Volume
            self.ReqOrderInsert(pTrade.InstrumentID,Direction=Direction,LimitPrice=LimitPrice,volume=volume,CombOffsetFlag=api.THOST_FTDC_OF_CloseToday)
            
        else:#平仓成交，持仓减少，挂单也减少
            msg+='平仓成交 '
            # self.orderDict[pTrade.InstrumentID]['orderInQueue']['volume']-=pTrade.Volume
            self.orderDict[pTrade.InstrumentID]['positionLong']['volume']-=pTrade.Volume
            # msg+=str(round(pTrade.Price,2))
            self.orderDict[pTrade.InstrumentID]['tradedCouple']['closeTime']=pTrade.TradeTime
            if self.orderDict[pTrade.InstrumentID]['tradedCouple']['openPrice']>0:
                self.orderDict[pTrade.InstrumentID]['tradedCouple']['closePrice']=-pTrade.Price
            else:
                self.orderDict[pTrade.InstrumentID]['tradedCouple']['closePrice']=pTrade.Price
            Thread(target=csvWrite,args=(pTrade.OrderSysID,self.orderDict[pTrade.InstrumentID]['tradedCouple']),).start()
        msg+=str(round(pTrade.Price,2))
        msg+=' '
        msg+=str(pTrade.Volume)+'手'
        print(msg,'持仓',self.orderDict[pTrade.InstrumentID]['positionLong']['volume'])

    def OnErrRtnOrderInsert(self,pInputOrder:'CThostFtdcInputOrderField', pRspInfo:'CThostFtdcRspInfoField')-> "void":
        print('1报单错误：',pRspInfo.ErrorMsg)

    def OnRspOrderInsert(self, pInputOrder: 'CThostFtdcInputOrderField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":      
        if pRspInfo.ErrorID!=0:
            print ("2报单错误：",pRspInfo.ErrorMsg)
    
    def OnRspOrderAction(self,pInputOrderAction:'CThostFtdcInputOrderActionField', pRspInfo:'CThostFtdcRspInfoField', nRequestID:'int',  bIsLast:'bool'):
        if pRspInfo.ErrorID!=0:
            print('撤单：',pRspInfo.ErrorMsg)
    def OnRspQryOrder(pOrder:'CThostFtdcOrderField', pRspInfo:'CThostFtdcRspInfoField', nRequestID:'int', bIsLast:'bool'):
        '''查询报单响应'''
        print(pOrder.InsertTime,'  ',pOrder.OrderSysID)
        # self.orderDict[pInputOrderAction.InstrumentID]['orderInQueue']['volume']=0
    def OnFrontDisconnected(nReason:'int'):
        print('断开连接,原因：',nReason)
    def orderSignalHandle(self,dc):
        # print(dc)
        InstrumentID=dc['InstrumentID']
        #开仓挂单和持仓之和达到最大手数
        if self.orderDict[InstrumentID]['orderInQueue']['volume']>0:return
        if self.orderDict[InstrumentID]['positionLong']['volume']>0:return
        
        #预平仓点数   VolPriceList
        self.orderDict[InstrumentID]['profitCoin']=dc['profitCoin']
        Beep(170,200)
        for limitPrice,volume in dc['VolPriceList']:
            self.ReqOrderInsert(dc['InstrumentID'],Direction=dc['direction'],LimitPrice=limitPrice,volume=volume,CombOffsetFlag=api.THOST_FTDC_OF_Open)
        
        self.orderDict[InstrumentID]['tradedCouple']['tragyName']=dc['tragyName']
        self.orderDict[InstrumentID]['tradedCouple']['tragyArg']=dc['tragyArg']
    def OnFrontConnected(self) -> "void":
        authfield = api.CThostFtdcReqAuthenticateField();
        authfield.BrokerID=BROKERID
        authfield.UserID=USERID
        authfield.AppID=APPID
        authfield.AuthCode=AUTHCODE
        self.tapi.ReqAuthenticate(authfield,0)
    
    def OnRspAuthenticate(self, pRspAuthenticateField: 'CThostFtdcRspAuthenticateField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":	
        if not pRspInfo.ErrorID :
            loginfield = api.CThostFtdcReqUserLoginField()
            loginfield.BrokerID=BROKERID
            loginfield.UserID=USERID
            loginfield.Password=PASSWORD
            loginfield.UserProductInfo="python dll"
            # print(pRspInfo.ErrorMsg)
            self.tapi.ReqUserLogin(loginfield,0)
        else:
            print('穿透式认证    失败')
            print ("BrokerID=",pRspAuthenticateField.BrokerID)
            print ("UserID=",pRspAuthenticateField.UserID)
            print ("AppID=",pRspAuthenticateField.AppID)
            print ("AppType=",pRspAuthenticateField.AppType)		
            print ("ErrorID=",pRspInfo.ErrorID)
            print ("ErrorMsg=",pRspInfo.ErrorMsg)
            exit()
        
    def OnRspUserLogin(self, pRspUserLogin: 'CThostFtdcRspUserLoginField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
        if pRspInfo.ErrorID:
            print("登陆   失败",pRspInfo.ErrorMsg)
            exit()
        else:#ErrorID==0
            print ("BrokerID:%s userID:%s"%(pRspUserLogin.BrokerID,pRspUserLogin.UserID))
            qryinfofield = api.CThostFtdcQrySettlementInfoField()
            qryinfofield.BrokerID=BROKERID
            qryinfofield.InvestorID=USERID
            qryinfofield.TradingDay=pRspUserLogin.TradingDay
            self.tapi.ReqQrySettlementInfo(qryinfofield,0)
        
    def OnRspQrySettlementInfo(self, pSettlementInfo: 'CThostFtdcSettlementInfoField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
        if  pSettlementInfo != None :
            print ("content:",pSettlementInfo.Content)
        else :
            print ("content null")
        pSettlementInfoConfirm=api.CThostFtdcSettlementInfoConfirmField()
        pSettlementInfoConfirm.BrokerID=BROKERID
        pSettlementInfoConfirm.InvestorID=USERID
        self.tapi.ReqSettlementInfoConfirm(pSettlementInfoConfirm,0)
        
    def OnRspSettlementInfoConfirm(self, pSettlementInfoConfirm: 'CThostFtdcSettlementInfoConfirmField', pRspInfo: 'CThostFtdcRspInfoField', nRequestID: 'int', bIsLast: 'bool') -> "void":
        if pRspInfo.ErrorID:
            print ("OnRspSettlementInfoConfirm")
            print ("ErrorID=",pRspInfo.ErrorID)
            print ("ErrorMsg=",pRspInfo.ErrorMsg)
            print ("send ReqorderfieldInsert ok")
            exit()
        else:
            
            print('确认结算单   成功')
        
    def ReqOrderInsert(self,InstrumentID,Direction=api.THOST_FTDC_D_Buy,LimitPrice=0,volume=1,CombOffsetFlag=api.THOST_FTDC_OF_Close):
        
        self.orderfield.InstrumentID=InstrumentID
        self.orderfield.Direction=Direction
        self.orderfield.VolumeTotalOriginal=volume  #LimitPrice
        self.orderfield.LimitPrice=LimitPrice
        self.orderfield.ExchangeID=self.info['instrumenID_dc'][InstrumentID]['exchange']
        self.orderfield.CombOffsetFlag=CombOffsetFlag     
        self.tapi.ReqOrderInsert(self.orderfield,0)

def csvWrite(OrderSysID,dc,tradeDate=TradeDateStr):
    with open('./trade.csv','a',encoding='utf8',newline='') as file_obj:
        CSV_writer = csv.writer(file_obj)
        row=(tradeDate,dc['instrumentID'],dc['openPrice'],dc['closePrice'],dc['openTime'],dc['closeTime'],dc['tragyName'],dc['tragyArg'],OrderSysID)
        CSV_writer.writerow(row)

class SignalHandle(QObject):
    '''信号总线，要继承QObject'''
    #signal的定义必须在init函数之外
    OrderSignal=pyqtSignal(dict)
    # CSVWriteSignal=pyqtSignal(dict)
class myWin(QMainWindow):
    def __init__(self,singnal,tdApi,waitThread):
        super().__init__()
        self.singnal=singnal
        self.tdApi=tdApi
        self.waitThread=waitThread
        
        self.tradespi=CTradeSpi(self.tdApi,info,singnal,orderfield)
        self.tdApi.RegisterSpi(self.tradespi)
        self.tdApi.SubscribePrivateTopic(api.THOST_TERT_QUICK)
        self.tdApi.SubscribePublicTopic(api.THOST_TERT_QUICK)
        self.tdApi.RegisterFront(FrontAddr)
        Thread(target=self.Join).start()
    def Join(self):
        self.tdApi.Init()
        self.waitThread.start()
        self.tdApi.Join()
    

    def closeEvent(self, a0):
        outBoll.QuitThread=True
        self.tdApi.Release()
        print('销毁线程')
        return super().closeEvent(a0)
def checkThread(signalHandle):
        '''检查天勤线程是否正常运行'''
        while 1:

            sleep(60)
def CTPThread():
    signalHandle=SignalHandle()
    tradeapi=api.CThostFtdcTraderApi_CreateFtdcTraderApi()
    # tradeapi.signalHandle=signalHandle
    waitThread=Thread(target=outBoll.TQWaiUpdate,args=(signalHandle,))
    app = QApplication(sys.argv)
    mWindow=myWin(signalHandle,tradeapi,waitThread)
    mWindow.show()
    sys.exit(app.exec())
    

	
if __name__=='__main__':
    CTPThread()